﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;

namespace BZLib
{
    public interface IPackable
    {
        BinaryString Pack();
    }

    public interface IUnpackable
    {
        BinaryString Unpack(BinaryString str);
    }

    public static class NetHelper
    {
        public static byte[] Reverse(this byte[] bytes)
        {
            int len = bytes.Length;
            byte[] temp = new byte[len];

            for (int i = 0; i < bytes.Length; i++)
            {
                temp[len - (i + 1)] = bytes[i];
            }

            return temp;
        }
        public static byte[] SwitchEndian(this byte[] bytes)
        {
            return bytes.Reverse();
        }
    }

    public class BinaryString : ICloneable
    {
        protected List<byte> buffer;
        public List<byte> Buffer { get { return buffer; } }
        public int Length { get { return buffer.Count; } }
        public byte this[int i] { get { return buffer[i]; } }

        #region Constructor Overloads
        public BinaryString(List<byte> _buffer)
        {
            buffer = _buffer;
        }
        public BinaryString(byte[] _buffer)
        {
            buffer = new List<byte>(_buffer);
        }
        public BinaryString()
        {
            buffer = new List<byte>();
        }
        public BinaryString(BinaryString str)
        {
            buffer = str.Buffer;
        }
        #endregion

        public BinaryString Copy(BinaryString str)
        {
            buffer = str.Buffer;
            return this;
        }
        public static BinaryString CopyTo(BinaryString from, BinaryString to)
        {
            to.buffer = from.buffer;
            return to;
        }

        #region Operator Overloads
        public static BinaryString operator +(BinaryString lhs, BinaryString rhs)
        {
            return new BinaryString(lhs).Add(rhs.ToArray());
        }
        public static BinaryString operator +(BinaryString lhs, byte[] rhs)
        {
            return new BinaryString(lhs).Add(rhs);
        }
        public static BinaryString operator +(BinaryString lhs, IPackable obj)
        {
            return new BinaryString(lhs).Add(obj);
        }
        public static BinaryString operator -(BinaryString lhs, BinaryString rhs)
        {
            BinaryString newStr = new BinaryString(lhs);
            newStr.Remove(Math.Min(lhs.Length, rhs.Length));
            return newStr;
        }
        public static BinaryString operator -(BinaryString lhs, byte[] rhs)
        {
            return lhs - new BinaryString(rhs);
        }
        public static BinaryString operator -(BinaryString lhs, IUnpackable obj)
        {
            BinaryString str = new BinaryString();
            BinaryString.CopyTo(obj.Unpack(lhs), str);
            return str;
        }
        #endregion
        #region Packing Overloads

        public static BinaryString operator +(BinaryString lhs, byte rhs)
        {
            return lhs.Add(rhs);
        }
        public static BinaryString operator +(BinaryString lhs, ushort rhs)
        {
            return lhs.Add(rhs);
        }
        public static BinaryString operator +(BinaryString lhs, short rhs)
        {
            return lhs.Add(rhs);
        }
        public static BinaryString operator +(BinaryString lhs, uint rhs)
        {
            return lhs.Add(rhs);
        }
        public static BinaryString operator +(BinaryString lhs, int rhs)
        {
            return lhs.Add(rhs);
        }
        public static BinaryString operator +(BinaryString lhs, float rhs)
        {
            return lhs.Add(rhs);
        }
        public static BinaryString operator +(BinaryString lhs, ulong rhs)
        {
            return lhs.Add(rhs);
        }
        public static BinaryString operator +(BinaryString lhs, long rhs)
        {
            return lhs.Add(rhs);
        }

        #endregion

        public byte[] ToArray()
        {
            return buffer.ToArray();
        }
        public override string ToString()
        {
            return BitConverter.ToString(buffer.ToArray());
        }

        protected BinaryString AddHBOBytes(byte[] data)
        {
            // bytes are in host byte order and may need converting to net byte order
            if (BitConverter.IsLittleEndian)
                data = data.SwitchEndian();

            return Add(data);
        }

        #region Add Overloads
        public BinaryString Add(byte[] bytes)
        {
            buffer.AddRange(bytes);
            return this;
        }
        public BinaryString Add(BinaryString str)
        {
            return Add(str.Buffer.ToArray());
        }
        public BinaryString Add(IPackable obj)
        {
            return Add(obj.Pack());
        }
        public BinaryString Add(byte data)
        {
            return Add(new byte[] { data });
        }
        public BinaryString Add(ushort data)
        {
            return AddHBOBytes(BitConverter.GetBytes(data));
        }
        public BinaryString Add(short data)
        {
            return AddHBOBytes(BitConverter.GetBytes(data));
        }
        public BinaryString Add(uint data)
        {
            return AddHBOBytes(BitConverter.GetBytes(data));
        }
        public BinaryString Add(int data)
        {
            return AddHBOBytes(BitConverter.GetBytes(data));
        }
        public BinaryString Add(float data)
        {
            return AddHBOBytes(BitConverter.GetBytes(data));
        }
        public BinaryString Add(ulong data)
        {
            return AddHBOBytes(BitConverter.GetBytes(data));
        }
        public BinaryString Add(long data)
        {
            return AddHBOBytes(BitConverter.GetBytes(data));
        }
        public BinaryString Add(string data, int len)
        {
            byte[] bytes = new byte[len];

            for (int i = 0; i < len; i++)
            {
                if (i >= data.Length) // Null pad
                    bytes[i] = 0;

                else
                    bytes[i] = BitConverter.GetBytes(data[i])[0];
            }

            return Add(bytes);
        }
        #endregion

        #region Remove Overloads
        public byte[] Remove(byte[] bytes)
        {
            buffer.RemoveRange(0, bytes.Length);
            return bytes;
        }
        public byte[] Remove(BinaryString str)
        {
            return Remove(str.ToArray());
        }
        public byte[] Remove(int num)
        {
            byte[] bytes = buffer.GetRange(0, num).ToArray();
            buffer.RemoveRange(0, num);
            return bytes;
        }
        public IUnpackable Remove(IUnpackable obj)
        {
            Copy(obj.Unpack(this));
            return obj;
        }
        #endregion

        #region Packing Methods
        public BinaryString Pack(IPackable obj)
        {
            return Add(obj);
        }
        public BinaryString PackUByte(byte data)
        {
            return Add(data);
        }
        public BinaryString PackUShort(ushort data)
        {
            return Add(data);
        }
        public BinaryString PackShort(short data)
        {
            return Add(data);
        }
        public BinaryString PackFloat(float data)
        {
            return Add(data);
        }
        public BinaryString PackUInt(uint data)
        {
            return Add(data);
        }
        public BinaryString PackInt(Int32 data)
        {
            return Add(data);
        }
        public BinaryString PackULong(ulong data)
        {
            return Add(data);
        }
        public BinaryString PackLong(long data)
        {
            return Add(data);
        }
        public BinaryString PackString(string data, int len)
        {
            return Add(data, len);
        }
        #endregion

        #region Unpacking Methods
        public IUnpackable Unpack(IUnpackable obj)
        {
            return Remove(obj);
        }
        public byte   UnpackUByte()
        {
            return Remove(sizeof(byte))[0];
        }
        public ushort UnpackUShort()
        {
            return BitConverter.ToUInt16(Remove(sizeof(ushort)), 0);
        }
        public short  UnpackShort()
        {
            return BitConverter.ToInt16(Remove(sizeof(short)), 0);
        }
        public float  UnpackFloat()
        {
            return BitConverter.ToSingle(Remove(sizeof(float)), 0);
        }
        public uint   UnpackUInt()
        {
            return BitConverter.ToUInt32(Remove(sizeof(uint)), 0);
        }
        public int    UnpackInt()
        {
            return BitConverter.ToInt32(Remove(sizeof(int)), 0);
        }
        public ulong  UnpackULong()
        {
            return BitConverter.ToUInt64(Remove(sizeof(ulong)), 0);
        }
        public long   UnpackLong()
        {
            return BitConverter.ToInt64(Remove(sizeof(long)), 0);
        }
        public string UnpackString(int len)
        {
            return new ASCIIEncoding().GetString(Remove(len));
        }
        #endregion

        #region Interface Implementations
        #region ICloneable

        public object Clone()
        {
            return new BinaryString(this.Buffer);
        }

        #endregion
        #endregion
    }
}